/*
 * Copyright 2011 Clean GWT
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.cleangwt.bootstrap.client.ui;

import java.util.ArrayList;
import java.util.List;

import com.cleangwt.bootstrap.client.SystemConstants;
import com.cleangwt.bootstrap.client.style.Styles;
import com.cleangwt.user.client.Elements;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.FontWeight;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;

/**
 * The alert block to display message with several types.
 * 
 * @author Jess
 * @version 1.0
 * @since 1.0
 * @see http://getbootstrap.com/javascript/#alerts
 */
public class Alert extends SimplePanel {

  private static class AlertPlaceholder {
    private FlowPanel placeholder = new FlowPanel();
    private AlertPlacement placement;
    private Timer autoClearingTimer;
    private List<Alert> alerts = new ArrayList<Alert>();

    private AlertPlaceholder() {
      RootPanel.get().add(placeholder);
      autoClearingTimer = new Timer() {

        @Override
        public void run() {
          Alert disposeAlert = null;
          for (Alert alert : alerts) {
            if (!Elements.hasClass(AlertType.ERROR.value, alert.getElement())) {
              disposeAlert = alert;
              break;
            }
          }

          if (disposeAlert == null) {
            autoClearingTimer.cancel();
          } else {
            disposeAlert.close();
          }
        }
      };
    }

    /**
     * Shows an alert.
     * 
     * @param alert the alert to show
     */
    void add(final Alert alert) {
      alerts.add(alert);
      if (placement.isStacked()) {
        placeholder.insert(alert, 0);
      } else {
        placeholder.add(alert);
      }
      // ensure center position
      if (placement == AlertPlacement.TOP || placement == AlertPlacement.BOTTOM) {
        int offset = (RootPanel.get().getOffsetWidth() - placeholder.getOffsetWidth()) >> 1;
        placeholder.getElement().getStyle().setMarginLeft(offset, Unit.PX);
      }
      Alert.onClosed(alert.getElement(), new Command() {

        @Override
        public void execute() {
          alerts.remove(alert);
          placeholder.remove(alert);
        }
      });
      autoClearingTimer.scheduleRepeating(VISIBLE_DURATION);
    }

    /**
     * Clears all visible alerts.
     */
    void reset() {
      autoClearingTimer.cancel();
      while (alerts.size() > 0) {
        alerts.get(0).close();
      }
    }

    /**
     * Sets the alert placement.
     * 
     * @param placement the alert placement
     */
    void setPlacement(AlertPlacement placement) {
      this.placement = placement;
      placeholder.setStyleName(PLACEHOLDER_STYLE);
      placeholder.addStyleName(placement.value);
    }
  }

  /**
   * The alert placement.
   */
  public enum AlertPlacement {
    TOP_LEFT("placeholder-top-left"), TOP("placeholder-top"), TOP_RIGHT("placeholder-top-right"), BOTTOM_LEFT(
        "placeholder-bottom-left"), BOTTOM("placeholder-bottom"), BOTTOM_RIGHT(
        "placeholder-bottom-right");
    private String value;

    private AlertPlacement(String style) {
      this.value = style;
    }

    /**
     * @return true to stake new alerts at top, false to add them at bottom
     */
    public boolean isStacked() {
      return this == BOTTOM_LEFT || this == BOTTOM || this == BOTTOM_RIGHT;
    }
  }

  /**
   * The alert type.
   */
  public enum AlertType {
    SUCCESS("alert-success"), INFO("alert-info"), WARNING("alert-warning"), ERROR("alert-danger");
    private String value;

    private AlertType(String style) {
      this.value = style;
    }
  }

  private static final String ALERT_STYLE = "alert";
  private static final String DISMISSABLE_STYLE = "alert-dismissable";
  private static final String PLACEHOLDER_STYLE = "alert-placeholder";
  private static AlertPlaceholder placeholder;
  private static final AlertPlacement defaultPlacement = AlertPlacement
      .valueOf(SystemConstants.Impl.defaultAlertPlacement());
  private static final int VISIBLE_DURATION = 2000;

  /**
   * Shows an WARNING alert with the given message at default placement.
   * 
   * @param message the message
   */
  public static void alert(String message) {
    // alert(message, AlertType.WARNING);
    Alert alert = new Alert(AlertType.WARNING, true);
    Element content = Document.get().createDivElement();
    content.getStyle().setFontWeight(FontWeight.BOLD);
    content.setInnerHTML(message);
    alert.getElement().appendChild(content);
    alert.show(defaultPlacement);
  }

  // /**
  // * Shows an alert with the given message and type at the default placement.
  // *
  // * @param message
  // * the message
  // * @param type
  // * the alert type
  // */
  // public static void alert(String message, AlertType type) {
  // Alert alert = new Alert(type, true);
  // Element content = Document.get().createDivElement();
  // content.getStyle().setFontWeight(FontWeight.BOLD);
  // content.setInnerHTML(message);
  // alert.getElement().appendChild(content);
  // alert.show(defaultPlacement);
  // }

  private static native void close(Element e) /*-{
                                              $wnd.jQuery(e).alert('close');
                                              }-*/;

  private static void ensurePlaceholder(AlertPlacement placement) {
    if (placeholder == null) {
      placeholder = new AlertPlaceholder();
    }
    if (placeholder.placement != placement) {
      placeholder.reset();
      placeholder.setPlacement(placement);
    }
  }

  private static native void onClose(Element e, Command cmd) /*-{
                                                             $wnd.jQuery(e).on('close.bs.alert', function() {
                                                             cmd.@com.google.gwt.user.client.Command::execute()();
                                                             })
                                                             }-*/;

  private static native void onClosed(Element e, Command cmd) /*-{
                                                              $wnd.jQuery(e).on('closed.bs.alert', function() {
                                                              cmd.@com.google.gwt.user.client.Command::execute()();
                                                              })
                                                              }-*/;

  /**
   * Creates an alert with the given 'type'.
   * 
   * @param type the alert type
   */
  public Alert(AlertType type) {
    this(type, false);
  }

  /**
   * Creates an alert with the given 'type' and 'dismissable'.
   * 
   * @param type the alert type
   * @param dismissable whether to add a default close button
   */
  public Alert(AlertType type, boolean dismissable) {
    setStyleName(ALERT_STYLE + " " + type.value);
    if (dismissable) {
      Element close = Document.get().createPushButtonElement();
      close.setClassName(Styles.CLOSE);
      close.setAttribute(DataModel.DATA_DISMISS, DataModel.TOGGLE_ALERT);
      close.setAttribute("aria-hidden", "true");
      close.setInnerHTML("&times;");
      this.getElement().appendChild(close);
      this.addStyleName(DISMISSABLE_STYLE);
    }
  }

  /**
   * Closes the alert.
   */
  public void close() {
    close(getElement());
  }

  /**
   * Shows the alert at the given placement.
   * 
   * @param placement the alert placement.
   */
  public void show(AlertPlacement placement) {
    ensurePlaceholder(placement);
    placeholder.add(this);

  }

}
